package mysql

import (
	"errors"
	"fmt"
	"gitee.com/captials-team/ubdframe/src/domain/configstc"
	"gitee.com/captials-team/ubdframe/src/domain/interfaces"
	"gitee.com/captials-team/ubdframe/src/domain/models"
	mysqlClients "gitee.com/captials-team/ubdframe/src/infrastructure/clients/mysql"
	"gitee.com/captials-team/ubdframe/src/pkg/logs"
	"gorm.io/gorm"
	"os"
	"time"
)

func NewOptimisticLock(conf configstc.DBConfig) *OptimisticLock {
	return &OptimisticLock{
		db:      mysqlClients.NewGormDB(conf),
		conf:    conf,
		timeout: 60,
	}
}

type OptimisticLock struct {
	db      *gorm.DB
	conf    configstc.DBConfig
	timeout int64 //锁超时时间，单位：秒
}

func (dao *OptimisticLock) WithTimeout(t int64) {
	dao.timeout = t
}

func (dao *OptimisticLock) Lock(name string) bool {
	findLock, err := dao.queryLock(name)
	if err != nil {
		logs.Out.Error("query lock fail:", err)
		return false
	}
	if findLock == nil {
		err := dao.addLock(name)
		if err != nil {
			logs.Out.Error("add lock fail:", err)
			return false
		}
		findLock, err = dao.queryLock(name)
		if err != nil {
			logs.Out.Error("after add query lock fail:", err)
			return false
		}
	} else if findLock.IsLock() {
		logs.Out.Error("query is lock fail:", err)
		return false
	}

	if findLock.LockedAt == nil {
		t0 := time.Now()
		findLock.LockedAt = &t0
	}

	lock, err := dao.lock(name, findLock.LockedAt.Unix())
	if err != nil {
		logs.Out.Error("lock fail:", err)
		return false
	}
	return lock
}

func (dao *OptimisticLock) UnLock(name string) bool {
	ok, err := dao.unLock(name)
	if err != nil {
		logs.Out.Error("unlock fail %s", err)
		return false
	}
	return ok
}

func (dao *OptimisticLock) queryLock(name string) (*models.OptimisticLock, error) {
	db := dao.db

	db = db.Select("lock_name,ifnull(lock_at,'') as lock_at,lock_host,timeout,status")
	var find models.OptimisticLock

	if ret := db.Where("`lock_name`=?", name).First(&find); errors.Is(ret.Error, gorm.ErrRecordNotFound) {
		return nil, nil
	} else if ret.Error != nil {
		return nil, ret.Error
	}
	return &find, nil
}

func (dao *OptimisticLock) addLock(name string) error {
	db := dao.db

	t := time.Now().Unix()
	table := new(models.OptimisticLock).TableName(dao.db.NamingStrategy)

	insertSql := fmt.Sprintf("INSERT INTO %s SET lock_name=?,status=?,lock_at=?,unlock_at=?", table)
	ret := db.Exec(insertSql, name, 0, t, t)

	if ret.Error != nil {
		return ret.Error
	}
	return nil
}

func (dao *OptimisticLock) lock(name string, lastLockAt int64) (bool, error) {
	db := dao.db

	lockAt := time.Now().Unix()
	hostname, _ := os.Hostname()
	table := new(models.OptimisticLock).TableName(dao.db.NamingStrategy)

	updateSql := fmt.Sprintf("UPDATE %s SET lock_at=?,lock_host=?,timeout=?,status=? WHERE lock_name=? AND lock_at=?", table)

	ret := db.Exec(updateSql,
		lockAt, hostname, dao.timeout, 1,
		name, lastLockAt)
	if ret.Error != nil {
		return false, ret.Error
	}

	return ret.RowsAffected == 1, nil
}

func (dao *OptimisticLock) unLock(name string) (bool, error) {
	db := dao.db
	unlockAt := time.Now().Unix()
	table := new(models.OptimisticLock).TableName(dao.db.NamingStrategy)

	updateSql := fmt.Sprintf("UPDATE %s SET unlock_at=?,status=? WHERE lock_name=? AND status=?", table)

	ret := db.Exec(updateSql, unlockAt, 0, name, 1)
	if ret.Error != nil {
		return false, ret.Error
	}
	return ret.RowsAffected == 1, nil
}

func (dao *OptimisticLock) Migrate() (int64, error) {
	return 1, dao.db.AutoMigrate(new(models.OptimisticLock))
}

func (dao *OptimisticLock) Use(db *gorm.DB) interfaces.ItfOptimisticLock {
	return &OptimisticLock{db: db}
}

func (dao *OptimisticLock) DB() *gorm.DB {
	return dao.db
}
